using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Jarvis.Server.Repositories;
using Ninject;
using Ninject.Extensions.Logging;

namespace Jarvis.Server.Location
{
    public class LocationProvider : ILocationProvider
    {
        private ILocationRepository _locations;
        private ISensorDatasProvider _sdp;
        private ILogger _logger;
        private IKernel _kernel;

        private Domain.Location _currentComputedLocation = null;


        public LocationProvider(ISensorDatasProvider sdp, ILocationRepository locations, ILoggerFactory loggerFactory, IKernel kernel)
        {
            if (sdp == null) throw new ArgumentNullException("sdp", "ISensorDataProvider dependency should be provided");
            if (locations == null) throw new ArgumentNullException("locations", "Repository dependency should be provided");
            _sdp = sdp;
            _locations = locations;
            _kernel = kernel;
            _logger = loggerFactory.GetCurrentClassLogger();

            var thread = new Thread(UpdateComputedLocation);
            thread.Name = "Current Location Updater";
            thread.IsBackground = true;
            thread.Start();
        }

        private void UpdateComputedLocation()
        {
            while (true)
            {


                var currentLocation = CurrentLocation;

                var locations = from location in _locations.All()
                                select location;

                //Forces Loading from db by constructing list in order to use order by clause
                var locationsList = locations.ToList().OrderBy(
                    location => location.LocationSensorDatas.DistanceFrom(currentLocation.LocationSensorDatas));

                var currentComputedLocation = locationsList.FirstOrDefault();

                _logger.Debug("Updated Current Location - {0}", (currentComputedLocation ?? new Domain.Location(){Name= "Null Location"}).Name);


                if (currentComputedLocation != null && _currentComputedLocation != null)
                {
                    if (currentComputedLocation.Id != _currentComputedLocation.Id)
                    {
                        lock (_currentComputedLocation)
                        {
                            _currentComputedLocation = currentComputedLocation;
                            OnCurrentLocationChanged(_currentComputedLocation);
                        }
                    }
                }


                Thread.Sleep(1000);
            }
        }

        #region Implementation of ILocationProvider

        /// <summary>
        /// Location object referring to the location the agent is currently
        /// in (not stored in the db of known locations) all fields are not populated
        /// </summary>
        public Domain.Location CurrentLocation
        {
            get
            {

                var sensorDatas = _sdp.GetCurrentSensorDatas();
                var location = new Domain.Location { LocationSensorDatas = sensorDatas };
                return location;
            }
        }

        /// <summary>
        /// Location object referring to the best fitting location present in the db.
        /// </summary>
        public Domain.Location ComputedLocation
        {
            get
            {
                lock (_currentComputedLocation)
                {
                    return _currentComputedLocation;
                }

            }
        }

        public IList<Domain.Location> KnownLocations
        {
            get { return _locations.All().ToList(); }
        }

        /// <summary>
        /// Stores the location object in the db of known locations
        /// </summary>
        /// <param name="location">location object to store, required fields should be populated</param>
        public void StoreAsKnownLocation(Domain.Location location)
        {
            
            //Forces proxy loading
            location.GetType();
            location.LocationSensorDatas = CurrentLocation.LocationSensorDatas;
            _kernel.Get<ILocationRepository>().Add(location);
        }

        public void RemoveLocation(Domain.Location location)
        {

            _locations.Delete(_locations.FindBy(location.Id));
        }

        public void UpdateLocation(Domain.Location location)
        {

            location.LocationSensorDatas = _locations.FindBy(location.Id).LocationSensorDatas;
            _locations.Merge(location);
        }

        public event EventHandler<LocationEventArgs> CurrentLocationChanged;

        public void OnCurrentLocationChanged(Domain.Location e)
        {
            EventHandler<LocationEventArgs> handler = CurrentLocationChanged;
            if (handler != null) handler(this, new LocationEventArgs(e));
        }

        #endregion



    }
}